/// <reference path="../disco/disco.model.ts" />
/// <reference path="../disco/disco.model.helper.ts" />
/// <reference path="../disco/disco.web.ts" />

module disco {
    export class Ontology {
        public dataContext: model.DataContext;
        private serviceUri: string;

        constructor(serviceUri: string) {
            this.serviceUri = serviceUri;
        }

        public isInitialized(): boolean {
            return this.dataContext != null;
        }

        public init(success: (context: model.DataContext) => void, error: (error) => void) {
            web.Requests.loadOntologyMetadata(this.serviceUri + "$metadata",
                metadata => {
                    var context = new model.DataContext();
                    var contextMgr = new model.helper.DataContextManager(context);

                    context.serviceUri = this.serviceUri;
                    context.metadata = metadata;
                    contextMgr.generateEntityModel();

                    this.dataContext = context;
                    success(context);
                },
                error);
        }

        public getEntityById(type: model.EntityType, id: number, success: (entity: model.Entity) => void, error: (error) => void) {
            var set = this.dataContext.entitySet(type.name);

            var existing = Collections.nullableSingle(set.rows(), e => e.property('Id').value == id);
            if (existing != null)
                success(existing);
            else
                this.getEntityByPath(set.name + '(' + id + ')', success, error);
        }

        public getEntityByPath(path: string, success: (entity: model.Entity) => void, error: (error) => void) {
            web.Requests.loadOntologyContent(this.serviceUri + path,
                data => {
                    success(this.registerFromRawEntity(data));
                },
                error);
        }

        public getEntitiesByPath(path: string, success: (entities: model.Entity[]) => void, error: (error) => void) {
            this.getEntitiesByUrl(this.serviceUri + path, success, error);
        }

        public getEntitiesByUrl(url: string, success: (entities: model.Entity[]) => void, error: (error) => void) {
            web.Requests.loadOntologyContent(url,
                data => {
                    var rawEntities: any[] = data.value || [data];
                    var entities: model.Entity[] = [];

                    rawEntities.forEach(rawEntity => {
                        entities.push(this.registerFromRawEntity(rawEntity));
                    });

                    success(entities);
                },
                error);
        }

        private registerFromRawEntity(rawEntity): model.Entity {
            var entityMgr = new model.helper.EntityManager(this.dataContext);
            var entity = entityMgr.fromRawEntity(rawEntity);

            var en: model.Entity = Collections.nullableSingle(entity.type.entitySet.rows(), e => e.property('Id').value == entity.property('Id').value)
            if (en)
                return en;

            var setMgr = new model.helper.EntitySetManager();
            setMgr.set(this.dataContext.entitySet(entity.type.name));

            setMgr.add(entity);
            return entity;
        }

        public getEntitiesByTemplate(template: any, success: (entities: model.Entity[]) => void, url: string = null) {
            var mode = (url == null) ? 'entitySet' : 'url';

            var constraints = {};
            var templates = {};
            var computedUrl: string;

            Object.keys(template.properties || {}).forEach(key => {
                var val = template.properties[key];

                if ((typeof val) == 'object' && Type.get(val) != '[object Array]')
                    templates[key] = val;
                else
                    constraints[key] = val;
            });

            if (mode == 'entitySet') {
                var filterPath: string = '?$filter=true';
                Object.keys(constraints).forEach(key => {
                    var val = constraints[key];
                    var str = val;
                    if (Type.get(val) != '[object Array]')
                        val = [val];
                    str = '(' + Collections.implode(val.map(v => key + ' eq ' + v), ' or ') + ')';
                    filterPath += ' and ' + str;
                });
                computedUrl = this.serviceUri + (<model.EntityType>template.type).entitySet.name + filterPath;
            }
            else if (mode == 'url') {
                computedUrl = url;
            }

            var self = this;
            this.getEntitiesByUrl(computedUrl,
                entities => {
                    var entityCallbacks: any[] = entities.map(entity => (onEntityReady => {
                        var propertyCallbacks: any[] = Object.keys(templates).map(key => (onPropertyReady => {
                            var property = entity.property(key);
                            var template = templates[key];
                            if (!property.property.isNavigation || property.value) {
                                onPropertyReady();
                                return;
                            }
                            self.getEntitiesByTemplate(template, propertyValue => {
                                if (property.property.isMany)
                                    property.value = propertyValue;
                                else if(propertyValue.length > 0)
                                    property.value = propertyValue[0];
                                onPropertyReady();
                            }, property.url);
                        }));
                        Callbacks.batch(propertyCallbacks, onEntityReady);
                    }));
                    Callbacks.batch(entityCallbacks, () => { success(entities) });
                },
                error => {
                });
        }

        //example: entityTemplate = { type: <Content>, properties: { Title: "Title", Text: "Text } }
        public addEntity(entityTemplate: any, success: (e: model.Entity) => void, error: (e) => void) {
            web.Requests.addEntity(this.dataContext.serviceUri + entityTemplate.type.entitySet.name,
                entityTemplate.properties || {},
                rawEntity => {
                    success(this.registerFromRawEntity(rawEntity));
                },
                err => {
                    error(err);
                });
        }

        public removeEntity(entityType: model.EntityType, entityId: any, success: () => void, error: (error) => void) {
            this.removeEntityByPath(entityType.entitySet.name + '(' + entityId + ')', success, error);
        }

        public removeEntityByPath(path: string, success: () => void, error: (error) => void) {
            this.removeEntityByUrl(this.dataContext.serviceUri + path, success, error);
        }

        public removeEntityByUrl(url: string, success: () => void, error: (error) => void) {
            web.Requests.removeEntity(url,
                d => {
                    success();
                },
                e => {
                    error(e);
                });
        }

        public addConnection(entityType: model.EntityType, entityId: any, property: model.EntityProperty, valueId: any, success: () => void, error: (error) => void) {
            if (!property.isNavigation)
                throw new Error('disco.Ontology.addConnection: operation is only valid for navigation properties.');
            web.Requests.addEntityConnection(
                this.dataContext.serviceUri + entityType.entitySet.name + '(' + entityId + ')', property.name,
                this.dataContext.serviceUri + property.type.entitySet.name + '(' + valueId + ')',
                success, error);
        }

        public removeConnection(entityType: model.EntityType, entityId: any, property: model.EntityProperty, valueId: any, success: () => void, error: (error) => void) {
            if (!property.isNavigation)
                throw new Error('disco.Ontology.addConnection: operation is only valid for navigation properties.');
            web.Requests.removeEntityConnection(
                this.dataContext.serviceUri + entityType.entitySet.name + '(' + entityId + ')',
                property.name + '(' + valueId + ')',
                success, error);
        }

        public clearCache() {
            Object.keys(this.dataContext.entitySets).forEach(key => {
                var set: model.EntitySet = this.dataContext.entitySets[key];
                set.rows([]);
            });
        }
    }
}